package com.example.handler.declare;

import com.example.util.CustomNotifier;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.XmlRecursiveElementVisitor;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.xml.XmlTag;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author Aaron
 * @since 2021/8/30 19:53
 * <p>描述：</p>
 */
public class XmlGoToDeclareHandler extends BaseGoToDeclareHandler {

    private final AnActionEvent anActionEvent;
    private final PsiFile psiFile;
    private final Project project;
    private final Module module;
    private final Editor editor;

    public XmlGoToDeclareHandler(AnActionEvent anActionEvent, PsiFile psiFile) {
        this.anActionEvent = anActionEvent;
        // 当前文件
        this.psiFile = psiFile;

        // 获取当前 Project
        this.project = anActionEvent.getProject();
        Objects.requireNonNull(project, "Can't get the project by the action event.");

        // 当前 Module
        ProjectFileIndex fileIndex = ProjectFileIndex.getInstance(project);
        this.module = fileIndex.getModuleForFile(psiFile.getContainingFile().getVirtualFile());
        Objects.requireNonNull(project, "Can't get the module by the action event.");

        // 获取当前 Editor
        this.editor = CommonDataKeys.EDITOR.getData(anActionEvent.getDataContext());
        Objects.requireNonNull(project, "Can't get the Editor by the action event.");
    }

    @Override
    public void doHandle() {
        super.doHandle();
        // 获取当前位置在 xml 文件中的绝对偏移值
        int currentPositionOffset = this.editor.getCaretModel().getOffset();
        // 保存 XML 映射文件中的 namespace 和 id 内容
        Map<String, String> namespaceIdMap = new HashMap<>(3);

        this.psiFile.accept(new XmlRecursiveElementVisitor() {
            // 上一个 XML 映射文件中 select/update/delete/insert 的 id
            String lastId = "";
            // 该 id 的绝对偏移量
            int lastOffset = 0;
            // 是否找到该 id
            boolean successful = false;

            @Override
            public void visitElement(@NotNull PsiElement element) {
                super.visitElement(element);
                if (!(element instanceof XmlTag)) {
                    return;
                }

                XmlTag xmlTag = (XmlTag) element;
                switch (xmlTag.getName()) {
                    case "mapper":
                        namespaceIdMap.put("namespace", xmlTag.getAttributeValue("namespace"));
                        break;
                    case "insert":
                    case "delete":
                    case "update":
                    case "select":
                        if (successful) {
                            break;
                        }

                        String id = xmlTag.getAttributeValue("id");
                        // 如果当前位置偏移量大于该标签的偏移量，说明要么该标签就是要找的标签，要么要找的标签还在下面
                        if (currentPositionOffset > xmlTag.getTextOffset()) {
                            lastOffset = xmlTag.getTextOffset();
                            lastId = id;
                            // 将该 id 保存起来，防止本 id 就是要找的标签时，以无结果退出
                            namespaceIdMap.put("id", id);
                            break;
                        }

                        // 此时当前位置偏移量已经小于当前标签的偏移量：
                        if (lastOffset == 0) {
                            // 当前位置的偏移量小于任何一个标签，则取大于当前位置偏移量的第一个标签
                            namespaceIdMap.put("id", id);
                        } else {
                            // 取小于当前位置偏移量的最近一个标签
                            namespaceIdMap.put("id", lastId);
                        }
                        successful = true;
                        break;
                    default:
                }
            }
        });

        if (!namespaceIdMap.containsKey("namespace") || !namespaceIdMap.containsKey("id")) {
            CustomNotifier.error(anActionEvent.getProject(), "namespace or id is not exists!");
            return;
        }

        // namespace 在 Mybatis 中的默认形式为 com.example.XxxMapper，这里要找的是 XxxMapper.java 文件
        String[] split = namespaceIdMap.get("namespace").split("\\.");
        String className = split[split.length - 1];
        CustomNotifier.info(anActionEvent.getProject(), "from " + this.psiFile.getName() + " to " + className);

        @NotNull PsiFile[] foundFiles = PsiShortNamesCache.getInstance(project).getFilesByName(String.format("%s.java", className));
        Arrays.stream(foundFiles).forEach(t -> this.doLocate(project, namespaceIdMap.get("id"), t));
    }

}
